// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Globalization;
using System.Xml.Serialization;
using System.IO;
using System.Diagnostics;
using System.Threading;
using System.Security.Permissions;

using Microsoft.TeamFoundation.Migration.Toolkit.Wit;

namespace Microsoft.TeamFoundation.Migration.Toolkit
{
    /// <summary>
    /// The class for SQL reporting in the migration toolkit
    /// </summary>
    /// 
    
    [XmlType("SQLReport")]
    public class SqlReportAdaptor : SqlConnectionDescription, IMigrationSessionEventSink
    {
        /// <summary>
        /// Creates a new instance of the SqlReportAdaptor
        /// </summary>
        
        public SqlReportAdaptor()
        {
            m_processId = MigrationUtil.GetProcessId();
        }

        #region IMigrationSessionEventSink Members

        /// <summary>
        /// Registers the report adaptor with the session object
        /// </summary>
        /// <param name="session"></param>
        public void RegisterSession(IMigrationSession session)
        {
            if (session == null)
            {
                throw new ArgumentNullException("session");
            }

            validateConnection();

            session.SessionAborted += new EventHandler<MigrationSessionEventArgs>(SessionEvent);
            session.SessionComplete += new EventHandler<MigrationSessionEventArgs>(SessionEvent);
            session.SessionStart += new EventHandler<MigrationSessionEventArgs>(SessionEvent);
            session.SessionError += new EventHandler<MigrationSessionEventArgs>(SessionEvent);

            VersionControlSession vcSession = session as VersionControlSession;
            if (vcSession != null)
            {
                registerVCSession(vcSession);
            }

            WorkItemTrackingSession witSession = session as WorkItemTrackingSession;
            if (witSession != null)
            {
                registerWitSession(witSession);
            }
        }

        /// <summary>
        /// Creates the database schema if necessary.
        /// </summary>
        private void validateLoggingDB()
        {
            // do the work in master since we know we can connect here
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(ConnectionString);
            bool deployFlag = false;
            builder.InitialCatalog = "master";

            // create a connection to the master database (since the database we'll create may not exist)
            using(SqlConnection conn = new SqlConnection(builder.ToString()))
            {
                conn.Open();
                if (!SqlUtil.DoesDbExist(conn, Database))
                {
                    SqlUtil.CreateDB(conn, Database);
                    deployFlag = true;
                }
            }

            if (deployFlag)
            {
                // create a new connection - this time to the real database
                using (SqlConnection conn = new SqlConnection(ConnectionString))
                {
                    // now create the tables/procs/etc in the created DB
                    createSchema(conn);
                }
            }
        }

        private static void createSchema(SqlConnection conn)
        {
            // Tables
            SqlUtil.ExecuteNamedResource("Microsoft.TeamFoundation.Migration.Toolkit.Reporting.SQL.Database.sql", conn);
            SqlUtil.ExecuteNamedResource("Microsoft.TeamFoundation.Migration.Toolkit.Reporting.SQL.Sprocs.sql", conn);
        }

        private void validateConnection()
        {
            if (!disposed)
            {
                lock (m_connLock)
                {
                    if (disposed)
                    {
                        throw new ObjectDisposedException("SqlReportAdaptor");
                    }

                    string connString = ConnectionString;

                    if (string.IsNullOrEmpty(connString))
                    {
                        throw new MigrationException(MigrationToolkitResources.SQLMissingConnectionString);
                    }
                }
            }

            validateLoggingDB();
        }

        private void registerVCSession(VersionControlSession vcSession)
        {
            vcSession.AnalysisAborted += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.AnalysisComplete += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.AnalysisError += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.AnalyzingChangeComplete += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.AnalyzingChangeStarting += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.AnalysisStarting += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.MigrationStarting += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.MigratingChangeComplete += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.MigratingChangeStarting += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.MigrationAborted += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.MigrationComplete += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.MigrationError += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.MigrationDownloadStarted += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.MigrationDownloadCompleted += new EventHandler<VersionControlEventArgs>(SessionEvent);
            vcSession.MigrationWarning += new EventHandler<VersionControlEventArgs>(SessionEvent);
        }

        private void registerWitSession(WorkItemTrackingSession witSession)
        {
            witSession.WorkItemMigrationStart += new EventHandler<WorkItemMigrationEventArgs>(SessionEvent);
            witSession.WorkItemMigrationComplete += new EventHandler<MigrationCompleteEventArgs>(SessionEvent);
            witSession.WorkItemMigrationFailed += new EventHandler<WorkItemMigrationEventArgs>(SessionEvent);
            witSession.WorkItemFieldConflict += new EventHandler<FieldConflictEventArgs>(SessionEvent);
        }


        #endregion

        #region Session level event handlers

        /// <summary>
        /// Logs the event to the DB.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
        private void SessionEvent(object sender, MigrationEventArgs e)
        {
            // this is a chatty protocol.  Perhaps a caching or queuing process is needed
            // to reduce the number of round trips to the server
            using (SqlConnection conn = new SqlConnection(ConnectionString))
            {
                conn.Open();
                using (SqlTransaction trn = conn.BeginTransaction())
                {
                    using (SqlCommand cmd = new SqlCommand())
                    {
                        cmd.Connection = conn;
                        cmd.Transaction = trn;
                        cmd.CommandType = CommandType.StoredProcedure;

                        // Set shared parameters
                        AddParam(cmd, "@UTCWhen", SqlDbType.DateTime, e.Time.ToUniversalTime());
                        AddParam(cmd, "@PID", SqlDbType.Int, m_processId);
                        AddParam(cmd, "@TID", SqlDbType.Int, Thread.CurrentThread.ManagedThreadId);
                        AddParam(cmd, "@Description", SqlDbType.NVarChar, e.Description);
                        AddParam(cmd, "@Exception", SqlDbType.NVarChar, e.Exception == null ? null : e.Exception.ToString());
                        if (e is MigrationSessionEventArgs)
                        {
                            SetSessionParams(cmd, sender, (MigrationSessionEventArgs)e);
                        }
                        else if (e is WorkItemMigrationEventArgs)
                        {
                            SetWorkItemParams(cmd, sender, (WorkItemMigrationEventArgs)e);
                        }
                        else if (e is VersionControlEventArgs)
                        {
                            SetVersionControlParams(cmd, sender, (VersionControlEventArgs)e);
                        }
                        else
                        {
                            Debug.Fail("Unknown event type!");
                            cmd.CommandText = "prc_iiLogEvent";
                            cmd.Parameters.Add("@Id", SqlDbType.Int).Direction = ParameterDirection.Output;
                        }

                        cmd.ExecuteNonQuery();
                        trn.Commit();
                    }
                }
            }
        }

        /// <summary>
        /// Stores session-specific information in the parameters.
        /// </summary>
        /// <param name="cmd">Target command</param>
        /// <param name="sender">Event sender (session)</param>
        /// <param name="e">Event</param>
        private static void SetSessionParams(
            SqlCommand cmd,
            object sender,
            MigrationSessionEventArgs e)
        {
            cmd.CommandText = "prc_iiLogSessionEvent";
            AddParam(cmd, "@SessionId", SqlDbType.NVarChar, ((IMigrationSession)sender).Id);
            AddParam(cmd, "@SessionType", SqlDbType.NVarChar, sender.GetType().AssemblyQualifiedName);
            AddParam(cmd, "@ItemCount", SqlDbType.Int, e.MigratedItemCount);
            AddParam(cmd, "@FailureCount", SqlDbType.Int, e.FailedItemCount);
        }

        /// <summary>
        /// Sets work item-specific information in the parameters.
        /// </summary>
        /// <param name="cmd">Target command</param>
        /// <param name="sender">Sender of the event</param>
        /// <param name="e">Event arguments</param>
        private static void SetWorkItemParams(
            SqlCommand cmd,
            object sender,
            WorkItemMigrationEventArgs e)
        {
            cmd.CommandText = "prc_iiLogWorkItemEvent";
            AddParam(cmd, "@SessionId", SqlDbType.NVarChar, ((IMigrationSession)sender).Id);
            AddParam(cmd, "@PrimarySystem", SqlDbType.NVarChar, e.PrimarySystem.ToString());
            AddParam(cmd, "@SourceId", SqlDbType.NVarChar, e.SourceId.ToString());
        }


        /// <summary>
        /// Sets version control-specific information in the parameters.
        /// </summary>
        /// <param name="cmd">Target command</param>
        /// <param name="sender">Sender of the event</param>
        /// <param name="e">Event arguments</param>
        private static void SetVersionControlParams(
            SqlCommand cmd,
            object sender,
            VersionControlEventArgs e)
        {
            cmd.CommandText = "prc_iiLogVersionControlEvent";
            AddParam(cmd, "@SessionId", SqlDbType.NVarChar, ((IMigrationSession)sender).Id);
            AddParam(cmd, "@PrimarySystem", SqlDbType.NVarChar, e.PrimarySystem.ToString());
        }

        /// <summary>
        /// Sets parameter to the given value.
        /// </summary>
        /// <param name="cmd">Command</param>
        /// <param name="name">Name of the parameter</param>
        /// <param name="type">Parameter type</param>
        /// <param name="value">Value of the parameter</param>
        private static void AddParam(
            SqlCommand cmd,
            string name,
            SqlDbType type,
            object value)
        {
            if (value == null)
            {
                value = DBNull.Value;
            }
            cmd.Parameters.Add(name, type).Value = value;
        }


        #endregion

        #region IDisposable Members

        protected virtual void Dispose(bool disposing)
        {
            lock (m_connLock)
            {
                if (!disposed)
                {
                    if (disposing)
                    {
                    }

                    // release unmanged resources
                }

                disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        bool disposed;

        #endregion

        object m_connLock = new object();
        int m_processId;

    }
}
